#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define FORMAT "[%s@%s %s]# "     // 命令行提示符格式
const size_t COMMANDLINE = 1024;  // 命令行提示符的最大长度
const size_t COMMANDSIZE = 128;   // 命令的最大长度

// 命令行参数表
const size_t MAXARGC = 128;
char* argv[MAXARGC];
int argc = 0;

// 当前工作目录
char pwd[MAXARGC];
char pwdenv[MAXARGC];

// 上一次工作目录
char oldpwdenv[MAXARGC];

// 环境变量表
char* env[MAXARGC];
int envs = 0;

// 重定向操作
#define NONE_REDIR 0
#define INPUT_REDIR 1
#define OUTPUT_REDIR 2
#define APPEND_REDIR 3

int redir;             // 重定向类型
std::string filename;  // 重定向目标文件

// 初始化环境变量
void InitEnviron()
{
    // 模拟从配置文件读取环境变量
    extern char** environ;
    for(int i=0; environ[i]; ++i)
    {
        env[i] = (char*)malloc(strlen(environ[i]));
        strcpy(env[i], environ[i]);
        envs++;
    }
    env[envs] = NULL;

    // 将环境变量加载到进程内部
    for(int i=0; env[i]; i++)
    {
        putenv(env[i]);
    }

    // 让全局的环境变量指针指向自己的环境变量表
    environ = env;
}


// 获取用户名
char* getUserName()
{
    return getenv("USER");
}


// 获取主机名
char* getHostName()
{
    return getenv("HOSTNAME");
}


// 获取当前工作目录
char* getPwd()
{
    char* s = getcwd(pwd, sizeof(pwd));
    if(s != NULL)
    {
        snprintf(pwdenv, sizeof(pwdenv), "PWD=%s", pwd);
        putenv(pwdenv);
    }
    return getenv("PWD");
}


// 只取出工作目录
std::string getPwdName()
{
    std::string pwd_name = getPwd();
    if(pwd_name.size() == 1) return pwd_name;
    size_t pos = pwd_name.rfind('/');
    return pwd_name.substr(pos+1, std::string::npos);
}


// 更新上一次的工作目录
void reOldPwd()
{
   snprintf(oldpwdenv, sizeof(pwdenv), "OLDPWD=%s", getenv("PWD"));
   putenv(oldpwdenv);
}


// 制作命令行提示符
void makeCommandLine(char* prompt, size_t size)
{
    //snprintf(prompt, size, FORMAT, getUserName(), getHostName(),getPwd());
    snprintf(prompt, size, FORMAT, getUserName(), getHostName(),getPwdName().c_str());
}


// 打印命令行提示符
void printCommandLinePrompt()
{
    char prompt[COMMANDLINE];
    makeCommandLine(prompt, sizeof(prompt));    
    printf("%s",prompt);
    fflush(stdout);
}


// 获取用户输入
bool getUserCommand(char* cmd, int size)
{
   const char* ans = fgets(cmd, size, stdin); 
   if(ans == NULL) return false;
   cmd[strlen(cmd) - 1] = '\0';
   return true;
}


// 切割命令，对命令进行分析
void commandParse(char* cmd)
{
    argc = 0;
    argv[argc++] = strtok(cmd, " ");
    while(NULL != (argv[argc++] = strtok(NULL, " "))) ;
    argc--;
}


// cd命令
void CD()
{
    if(argc == 1)
    {
        reOldPwd();
        chdir(getenv("HOME"));
    }
    else
    {
        std::string where = argv[1];
        if(where == "~")
        {
            reOldPwd();
            chdir(getenv("HOME"));
        }
        else if(where == "-")
        {
            chdir(getenv("OLDPWD"));
            reOldPwd();
        }
        else
        {
            reOldPwd();
            chdir(argv[1]);
        }
    }
}


// 执行内建命令
bool checkAndExectueBulidIn()
{
    std::string cmd = argv[0];
    if(cmd == "cd")
    {
        CD();
        return true;
    }
    else if(cmd == "echo")
    {

        return true;
    }
    return false;
}

// 执行命令
void exectue()
{
    pid_t id = fork();
    if(id == 0)
    {
        //  child
        if(redir == 1) // 输入重定向
        {
            int fd = open(filename.c_str(), O_RDONLY);
            if(fd < 0)
            {
                perror("open");
                exit(1);
            }
            dup2(fd, 0);
            close(fd);
        }
        else if(redir == 2) // 输出重定向
        {
            int fd = open(filename.c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0666);
            if(fd < 0)
            {
                perror("open");
                exit(2);
            }
            dup2(fd, 1);
            close(fd);
        }
        else if(redir == 3) // 追加重定向
        {
            int fd = open(filename.c_str(), O_CREAT | O_WRONLY | O_APPEND, 0666);
            if(fd < 0)
            {
                perror("open");
                exit(3);
            }
            dup2(fd, 1);
            close(fd);
        }
        execvp(argv[0], argv);
        exit(1);
    }
    
    // father
    int status = 0;
    pid_t rid = waitpid(id, &status, 0); // 阻塞等待
    if(rid < 0)
    {
        exit(2);
    }
}

void skipSpace(char cmd[], int& end)
{
    while(isspace(cmd[end])) end++;
}

void reDir(char cmd[])
{
    redir = NONE_REDIR;
    filename.clear();
    int begin = 0;
    int end = strlen(cmd)- 1;
    
    // 判断重定向类型
    while(end > begin)
    {
        if(cmd[end] == '<')
        {
            redir = INPUT_REDIR;
            cmd[end++] = 0;
            skipSpace(cmd, end);
            filename = cmd+end;
        }
        else if(cmd[end] == '>')
        {
            if(cmd[end-1] == '>')
            {
                redir = APPEND_REDIR;
                cmd[end-1] = 0;
            }
            else
            {
                redir = OUTPUT_REDIR;
            }
            cmd[end++] = 0;
            skipSpace(cmd, end);
            filename = cmd+end;
        }
        else
        {
            end--;
        }
    }
}


int main()
{
    InitEnviron();

    while(1)
    {
        printCommandLinePrompt();

        char command[COMMANDSIZE];
        if(!getUserCommand(command, sizeof(command)))
            continue;

        reDir(command);
        
        commandParse(command);

        if(checkAndExectueBulidIn())
            continue;
        
        exectue();
    }
    
    return 0;
}
